#pragma once
#include <utl/vector.h>
#include <utl/deque.h>
#include <gfx/dx12/helper.h>
#include <utl/memory.h>
#include <gfx/dx12/core.h>

namespace primal
{
	namespace gfx
	{


		//constexpr D3D12_COMMAND_LIST_TYPE TYPE{}; constexpr u64 FRAME_COUNT{4}; constexpr u64 FRAME_1 = FRAME_COUNT - 1;
		struct Frame
		{
			Frame();
			ComPtr<ID3D12CommandAllocator> allocator;
			utl::queue<ID3D12GraphicsCommandListN*> list_cache;
			utl::vector<ID3D12GraphicsCommandListN*> active_lists;
			u64 local_fence_value;
			HANDLE local_fence_event;
		};

		class Command
		{
		public:
			//static_assert(FRAME_COUNT == 2u || FRAME_COUNT == 4u || FRAME_COUNT == 8u || FRAME_COUNT == 16u || FRAME_COUNT == 32u || FRAME_COUNT == 64u);
			Command();
			void WaitFrame(HANDLE e, u64 fv);
			void Flush();
		protected:
			ID3D12GraphicsCommandListN* Internal_AcquireNextList(Frame* frm, D3D12_COMMAND_LIST_TYPE, ID3D12PipelineState* pipeline = nullptr);
			u64 fence_value;
			ComPtr<ID3D12Fence> fence;
		};

		template<u64 FRAME_COUNT, u64 FRAME_1 = FRAME_COUNT - 1ull>
		class DirectCommand : public Command
		{
		public:
			DirectCommand();
			void Begin();
			u64 End();
			ID3D12GraphicsCommandListN* AcquireNextList(ID3D12PipelineState* pipeline = nullptr);
		private:
			u64 frame_index;
			Frame frames[FRAME_COUNT];
		};

		template<u64 FRAME_COUNT, u64 FRAME_1>
		inline DirectCommand<FRAME_COUNT, FRAME_1>::DirectCommand() :
			Command{},
			frames{},
			frame_index{}
		{
			for (Frame& item : frames)
			{
				DXASSERT(device()->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IIDPPV(item.allocator)));
				item.local_fence_event = CreateEventA(nullptr, FALSE, FALSE, nullptr);
			}
		}

		template<u64 FRAME_COUNT, u64 FRAME_1>
		inline void DirectCommand<FRAME_COUNT, FRAME_1>::Begin()
		{
			Frame& cur_frame{ frames[frame_index] };
			WaitFrame(cur_frame.local_fence_event, cur_frame.local_fence_value);
			cur_frame.allocator->Reset();
		}

		template<u64 FRAME_COUNT, u64 FRAME_1>
		inline u64 DirectCommand<FRAME_COUNT, FRAME_1>::End()
		{
			Frame& cur_frame{ frames[frame_index] };
			for (auto& item : cur_frame.active_lists)
			{
				DXASSERT(item->Close());
				cur_frame.list_cache.push(item);
			}

			++fence_value;
			direct_queue()->ExecuteCommandLists(static_cast<u32>(cur_frame.active_lists.size()), (ID3D12CommandList**)(cur_frame.active_lists.data()));
			direct_queue()->Signal(this->fence.Get(), fence_value);
			++frame_index;
			frame_index &= FRAME_1;
			cur_frame.local_fence_value = fence_value;
			cur_frame.active_lists.clear();
			return fence_value;
		}

		template<u64 FRAME_COUNT, u64 FRAME_1>
		inline ID3D12GraphicsCommandListN* DirectCommand<FRAME_COUNT, FRAME_1>::AcquireNextList(ID3D12PipelineState* pipeline)
		{
			return Internal_AcquireNextList(&frames[frame_index], D3D12_COMMAND_LIST_TYPE_DIRECT, pipeline);
		}

		using DirectCMD = DirectCommand<COMMAND_FRAME_COUNT>;

		class CopyCommand : public Command
		{
		public:
			CopyCommand()
			{}

			~CopyCommand()
			{}

		private:

		};


		//inline Command<TYPE, FRAME_COUNT, FRAME_1>::Command(ID3D12DeviceN* d) : ComPtr<ID3D12CommandQueue>{}, device{ d }
		//{
		//	D3D12_COMMAND_QUEUE_DESC desc{};
		//	desc.Type = TYPE;

		//	DXASSERT(device->CreateCommandQueue(&desc, IID_PPV_ARGS(this->GetAddressOf())));

		//	for (auto& item : this->frames)
		//	{
		//		DXASSERT(device->CreateCommandAllocator(TYPE, IIDPPV(item.allocator)));
		//		item.local_fence_event = CreateEventA(nullptr, FALSE, FALSE, nullptr);
		//		assert(item.local_fence_event != nullptr);
		//	}

		//	DXASSERT(device->CreateFence(this->fence_value, D3D12_FENCE_FLAG_NONE, IIDPPV(this->fence)));

		//}

		//template<D3D12_COMMAND_LIST_TYPE TYPE, u64 FRAME_COUNT, u64 FRAME_1>
		//inline ID3D12GraphicsCommandListN* Command<TYPE, FRAME_COUNT, FRAME_1>::AcquireNextList(u64 fi, ID3D12PipelineState* pipeline)
		//{
		//	auto& cur_frame{ this->frames[fi] };
		//	ID3D12GraphicsCommandList6* temp_list;
		//	if (cur_frame.list_cache.empty())
		//	{
		//		DXASSERT(this->device->CreateCommandList1(0u, TYPE, D3D12_COMMAND_LIST_FLAG_NONE, IID_PPV_ARGS(&temp_list)));
		//	}
		//	else
		//	{
		//		temp_list = cur_frame.list_cache.front();
		//		cur_frame.list_cache.pop();
		//	}
		//	temp_list->Reset(cur_frame.allocator.Get(), pipeline);
		//	cur_frame.active_lists.push_back(temp_list);
		//	return temp_list;
		//}

		//template<D3D12_COMMAND_LIST_TYPE TYPE, u64 FRAME_COUNT, u64 FRAME_1>
		//inline u64 Command<TYPE, FRAME_COUNT, FRAME_1>::Begin()
		//{
		//	auto cur_frame_index{ this->frame_index++ };
		//	cur_frame_index &= FRAME_1;
		//	auto& cur_frame{ this->frames[cur_frame_index] };
		//	if (fence->GetCompletedValue() < cur_frame.local_fence_value)
		//	{
		//		fence->SetEventOnCompletion(cur_frame.local_fence_value, cur_frame.local_fence_event);
		//		WaitForSingleObject(cur_frame.local_fence_event, INFINITE);
		//	}
		//	cur_frame.allocator->Reset();
		//	return cur_frame_index;
		//}

		//template<D3D12_COMMAND_LIST_TYPE TYPE, u64 FRAME_COUNT, u64 FRAME_1>
		//inline u64 Command<TYPE, FRAME_COUNT, FRAME_1>::End(u64 fi)
		//{
		//	auto cur_fence_value{ ++this->fence_value };
		//	auto& cur_frame{ this->frames[fi] };

		//	for (auto& item : cur_frame.active_lists)
		//	{
		//		DXASSERT(item->Close());
		//		cur_frame.list_cache.push(item);
		//	}

		//	/*for (u32 i{}; i < size; ++i)
		//	{
		//		DXASSERT(lists[i]->Close());
		//		cur_frame.list_cache.push(lists[i]);
		//	}*/

		//	this->Get()->ExecuteCommandLists(static_cast<u32>(cur_frame.active_lists.size()),(ID3D12CommandList**)(cur_frame.active_lists.data()));
		//	cur_frame.local_fence_value = cur_fence_value;
		//	cur_frame.active_lists.clear();

		//	this->Get()->Signal(this->fence.Get(), cur_fence_value);
		//	return cur_fence_value;
		//}

		//template<D3D12_COMMAND_LIST_TYPE TYPE, u64 FRAME_COUNT, u64 FRAME_1>
		//inline void Command<TYPE, FRAME_COUNT, FRAME_1>::WaitFrame(HANDLE e, u64 arg)
		//{
		//	auto fv{ this->fence->GetCompletedValue() };
		//	if (fv < arg)
		//	{
		//		this->fence->SetEventOnCompletion(arg, e);
		//		WaitForSingleObject(e, INFINITE);
		//	}
		//}

		//template<D3D12_COMMAND_LIST_TYPE TYPE, u64 FRAME_COUNT, u64 FRAME_1>
		//inline void Command<TYPE, FRAME_COUNT, FRAME_1>::Flush()
		//{
		//	auto e{ CreateEventA(nullptr, FALSE, FALSE, nullptr) };
		//	WaitFrame(e, this->fence_value);
		//	CloseHandle(e);
		//}

		//template<D3D12_COMMAND_LIST_TYPE TYPE, u64 FRAME_COUNT, u64 FRAME_1>
		//inline u64 Command<TYPE, FRAME_COUNT, FRAME_1>::GetCount()
		//{
		//	return FRAME_COUNT;
		//}

		//using DirectCommand = Command<D3D12_COMMAND_LIST_TYPE_DIRECT, COMMAND_FRAME_COUNT>;

		//class CopyCommand : ComPtr<ID3D12CommandQueue>
		//{
		//public:
		//	CopyCommand(ID3D12DeviceN*);
		//	~CopyCommand();
		//	Frame* begin();
		//	void end(Frame* frm, bool is_wait);
		//	ID3D12GraphicsCommandListN* AcquireNextList(Frame* frm);
		//	void wait(Frame* frm);
		//private:
		//	std::mutex frame_locker;
		//	utl::queue<Frame*> frame_cache;
		//	ComPtr<ID3D12Fence> fence;
		//	std::atomic_uint64_t fence_value;
		//	ID3D12DeviceN* device;
		//};

		//constexpr D3D12_COMMAND_LIST_TYPE copy_command_type{ D3D12_COMMAND_LIST_TYPE_COPY };

		//inline CopyCommand::CopyCommand(ID3D12DeviceN* d) :
		//	ComPtr<ID3D12CommandQueue>{},
		//	frame_locker{},
		//	frame_cache{},
		//	fence{},
		//	fence_value{},
		//	device{ d }
		//{
		//	D3D12_COMMAND_QUEUE_DESC queue_desc{};
		//	queue_desc.Type = copy_command_type;

		//	DXASSERT(device->CreateCommandQueue(&queue_desc, IID_PPV_ARGS(this->GetAddressOf())));
		//	DXASSERT(device->CreateFence(fence_value, D3D12_FENCE_FLAG_NONE, IIDPPV(fence)));
		//}

		//inline CopyCommand::~CopyCommand()
		//{}

		//inline ID3D12GraphicsCommandListN* CopyCommand::AcquireNextList(Frame* frm)
		//{
		//	ID3D12GraphicsCommandList6* temp_list;
		//	if (frm->list_cache.empty())
		//	{
		//		DXASSERT(this->device->CreateCommandList1(0u, copy_command_type, D3D12_COMMAND_LIST_FLAG_NONE, IID_PPV_ARGS(&temp_list)));
		//	}
		//	else
		//	{
		//		temp_list = frm->list_cache.front();
		//		frm->list_cache.pop();
		//	}
		//	temp_list->Reset(frm->allocator.Get(), nullptr);
		//	frm->active_lists.push_back(temp_list);
		//	return temp_list;
		//}

		//inline Frame* CopyCommand::begin()
		//{
		//	Frame* frm{};
		//	if (!frame_cache.empty())
		//	{
		//		std::lock_guard lg{ frame_locker };
		//		if (!frame_cache.empty())
		//		{
		//			frm = frame_cache.front();
		//			frame_cache.pop();
		//		}
		//	}
		//	else
		//	{
		//		frm = utl::make_pointer<Frame>();
		//		DXASSERT(device->CreateCommandAllocator(copy_command_type, IIDPPV(frm->allocator)));
		//		frm->local_fence_event = CreateEventA(nullptr, FALSE, FALSE, nullptr);
		//	}
		//	assert(frm != nullptr);

		//	if (fence->GetCompletedValue() < frm->local_fence_value)
		//	{
		//		fence->SetEventOnCompletion(frm->local_fence_value, frm->local_fence_event);
		//		WaitForSingleObject(frm->local_fence_event, INFINITE);
		//	}

		//	frm->allocator->Reset();

		//	return frm;
		//}

		//inline void CopyCommand::wait(Frame* frm)
		//{
		//	if (fence->GetCompletedValue() < frm->local_fence_value)
		//	{
		//		fence->SetEventOnCompletion(frm->local_fence_value, frm->local_fence_event);
		//		WaitForSingleObject(frm->local_fence_event, INFINITE);
		//	}
		//}

		//inline void CopyCommand::end(Frame* frm, bool is_wait)
		//{
		//	for (auto& item : frm->active_lists)
		//	{
		//		DXASSERT(item->Close());
		//		frm->list_cache.push(item);
		//	}

		//	ptr_->ExecuteCommandLists(static_cast<u32>(frm->active_lists.size()), (ID3D12CommandList**)(frm->active_lists.data()));
		//	frm->local_fence_value = (++fence_value);
		//	frm->active_lists.clear();
		//	ptr_->Signal(fence.Get(), frm->local_fence_value);

		//	if (is_wait)
		//	{
		//		wait(frm);
		//	}

		//	std::lock_guard lg{ frame_locker };
		//	frame_cache.push(frm);
		//}
	}
}